﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Red.Core.UndoRedo;

namespace Red.GameData.Actions
{
    /// <summary>
    /// Set a value of a property
    /// </summary>
    public class SetValue : IAction
    {
        /// <summary>
        /// Target context of objects being edited
        /// </summary>
        private Red.GameData.EditorContext Context;

        /// <summary>
        /// Id of the object to modify
        /// </summary>
        private Guid ObjectID;

        /// <summary>
        /// Name of the property in the object to modify
        /// </summary>
        private string PropertyName;

        /// <summary>
        /// In case of array elements being modified - the GUID of the value element, NOTE - this is always set for array elements
        /// </summary>
        private Guid ValueGuid;

        /// <summary>
        /// Value to set
        /// </summary>
        private string Value;

        /// <summary>
        /// [UNDO] Previous value of the property, if NULL then property DID not exist in the original object
        /// </summary>
        private string PreviousValue;

        /// <summary>
        /// Get user name for this action
        /// </summary>
        /// <returns></returns>
        string IAction.GetDisplayName()
        {
            return "Set value of '" + PropertyName + "'"; // TODO: more descriptive please :)
        }

        /// <summary>
        /// Execute action
        /// </summary>
        bool IAction.Do(Red.Core.UndoRedo.ActionHistory history, ref string errorMessage)
        {
            // get the target object
            var obj = Context.Data.Find(ObjectID);
            if (obj == null)
            {
                errorMessage = "Unable to retrieve target object: " + Red.GameData.Raw.Utils.FormatGuid(ObjectID);
                return false;
            }

            // create property holder
            var prop = obj.GetProperty(PropertyName);
            if (prop == null)
            {
                errorMessage = "Unable to retrieve property '" + PropertyName + "' for object: " + Red.GameData.Raw.Utils.FormatGuid(ObjectID);
                return false;
            }

            // set property value
            if ( ValueGuid == Guid.Empty )
            {
                // this cannot fail
                prop.Action_ModifyValue( Value);
            }
            else
            {
                // modify specific value ID
                prop.Action_CreateValue(ValueGuid, Value);
            }

            // action was successful
            return true;
        }

        /// <summary>
        /// Revert execution of the action
        /// </summary>
        bool IAction.Undo(Red.Core.UndoRedo.ActionHistory history, ref string errorMessage)
        {
            // get the target object
            var obj = Context.Data.Find(ObjectID);
            if (obj == null)
            {
                errorMessage = "Unable to retrieve target object: " + Red.GameData.Raw.Utils.FormatGuid(ObjectID);
                return false;
            }

            // create property holder
            var prop = obj.GetProperty(PropertyName);
            if (prop == null)
            {
                errorMessage = "Unable to retrieve property '" + PropertyName + "' for object: " + Red.GameData.Raw.Utils.FormatGuid(ObjectID);
                return false;
            }

            // set property value
            if (ValueGuid == Guid.Empty)
            {
                // if we don't have a "previous" value captured for this property it means that it was NOT overridden, remove the property holder
                if (PreviousValue == null)
                {
                    // NOTE: the object is modified
                    obj.Action_RemovePropertyOverride(PropertyName);
                }
                else
                {
                    // this cannot fail
                    prop.Action_ModifyValue(PreviousValue);
                }
            }
            else
            {
                // restore specific value ID - this can fail if array element was NOT created yet
                // when restoring to invalid state default to empty string
                var valueToSet = (PreviousValue != null) ? PreviousValue : "";
                if (!prop.Action_SetValue(ValueGuid, valueToSet))
                {
                    errorMessage = "Unable to modify selected value for property '" + PropertyName + "' for object: " + Red.GameData.Raw.Utils.FormatGuid(ObjectID);
                    return false;
                }
            }

            // done
            return true;
        }


        /// <summary>
        /// Construct simple action for the whole property
        /// </summary>
        public static SetValue Create(EditorContext context, Raw.DataObject obj, string propertyName, string value, Guid valueId)
        {
            var ret = new SetValue();
            ret.Context = context;
            ret.ObjectID = obj.ID;
            ret.PropertyName = propertyName;
            ret.Value = value;
            ret.ValueGuid = valueId;
            ret.PreviousValue = null;

            // get current value of the property
            var prop = obj.GetPropertyNoCreate(propertyName);
            if (prop != null)
            {
                // single value
                if (valueId == Guid.Empty)
                {
                    // we expect single or NO value only
                    var values = prop.Values;
                    Debug.Assert(values.Count <= 1);

                    // store the previous value
                    if (values.Count == 1)
                        ret.PreviousValue = values[0].Value;
                }

                // array value
                else
                {
                    // find the matching value
                    foreach (var data in prop.Values)
                    {
                        if (data.ID == valueId)
                        {
                            ret.PreviousValue = data.Value;
                            break;
                        }
                    }
                }
            }

            // return created action
            return ret;
        }

    }
}
